﻿namespace System.Collections.Generic {

    public interface IListDataUpdated<T> : IChangable {

        public delegate void UpdatedDelegate(int index, T oldValue, T newValue);

        event UpdatedDelegate Updated;
    }

    public interface IListDataAdded<T> : IChangable {

        public delegate void AddedDelegate(int index, T newValue);

        event AddedDelegate Added;
    }

    public interface IListDataRemoved<T> : IChangable {

        public delegate void RemovedDelegate(int index, T oldValue);

        event RemovedDelegate Removed;
    }

    public interface IListDataCleared<T> : IChangable {

        public delegate void ClearedDelegate();

        event ClearedDelegate Cleared;
    }

    public interface IListDataEventDispatcher<T> : IListDataUpdated<T>, IListDataAdded<T>, IListDataRemoved<T>, IListDataCleared<T> {
    }

    public interface IReadOnlyListData<T> : IListDataEventDispatcher<T>, IReadOnlyList<T> {

        T Get(int index);

        bool TryGet(int index, out T value);

        IListData<T> AsReadWrite();
    }

    public interface IFixedLengthListData<T> : IListDataUpdated<T> {

        void Update(int index, T value);

        void Update(int index, Action<T> updater);
    }

    public interface IListData<T> : IListDataEventDispatcher<T>,
        IReadOnlyListData<T>, IFixedLengthListData<T>,
        IListDataAdded<T>, IListDataRemoved<T>, IListDataCleared<T>,
        IListDataUpdated<T>, IChangable, IList<T>, IReadOnlyList<T>, IEnumerable<T>, IEnumerable {

        new int Count { get; }

        new T this[int index] { get; set; }

        IReadOnlyListData<T> AsReadOnly();

        void NotifyChanged();
    }

    [Implement(typeof(IListDataEventDispatcher<>))]
    [Implement(typeof(IReadOnlyListData<>))]
    [Implement(typeof(IReadOnlyList<>))]
    [Implement(typeof(IListData<>))]
    [Implement(typeof(IList<>))]
    internal sealed class KHBD_ListData<T> : KHBD_Object, IListData<T> {

        public KHBD_ListData() : base() => m_This = this;

        private IListData<T> m_This;
        private IList<T> m_List = new List<T>();

        public event IChangable.ChangedDelegate Changed;
        public event IListData<T>.ClearedDelegate Cleared;
        public event IListData<T>.AddedDelegate Added;
        public event IListData<T>.UpdatedDelegate Updated;
        public event IListData<T>.RemovedDelegate Removed;

        int IReadOnlyCollection<T>.Count => m_List.Count;
        int ICollection<T>.Count => m_List.Count;
        int IListData<T>.Count => m_List.Count;

        bool ICollection<T>.IsReadOnly => m_List.IsReadOnly;

        T IReadOnlyList<T>.this[int index] => m_This[index];
        T IList<T>.this[int index] { get => m_This[index]; set => m_This[index] = value; }
        T IListData<T>.this[int index] {
            get => m_List[index];
            set => m_This.Update(index, value);
        }

        bool ICollection<T>.Contains(T item) => m_List.Contains(item);

        IEnumerator IEnumerable.GetEnumerator() => m_List.GetEnumerator();
        IEnumerator<T> IEnumerable<T>.GetEnumerator() => m_List.GetEnumerator();

        void ICollection<T>.Add(T item) {
            m_List.Add(item);
            if (!KHBD.Context.IsApplicationQuit) {
                Added?.Invoke(m_List.Count - 1, item);
            }
            Dirty();
        }

        void IList<T>.Insert(int index, T item) {
            m_List.Insert(index, item);
            if (!KHBD.Context.IsApplicationQuit) {
                Added?.Invoke(index, item);
            }
            Dirty();
        }

        void IFixedLengthListData<T>.Update(int index, T item) {
            var oldItem = m_List[index];
            m_List[index] = item;
            if (!KHBD.Context.IsApplicationQuit) {
                Updated?.Invoke(index, oldItem, item);
            }
            Dirty();
        }

        void IFixedLengthListData<T>.Update(int index, Action<T> updater) {
            var item = m_List[index];
            updater(item);
            if (!KHBD.Context.IsApplicationQuit) {
                Updated?.Invoke(index, item, item);
            }
            Dirty();
        }

        void IList<T>.RemoveAt(int index) {
            var oldItem = m_List[index];
            m_List.RemoveAt(index);
            if (!KHBD.Context.IsApplicationQuit) {
                Removed?.Invoke(index, oldItem);
            }
            Dirty();
        }

        bool ICollection<T>.Remove(T item) {
            var index = m_List.IndexOf(item);
            if (index > -1) {
                m_List.RemoveAt(index);
                if (!KHBD.Context.IsApplicationQuit) {
                    Removed?.Invoke(index, item);
                }
                Dirty();
                return true;
            }
            return false;
        }

        void ICollection<T>.Clear() {
            m_List.Clear();
            if (!KHBD.Context.IsApplicationQuit) {
                Cleared?.Invoke();
            }
            Dirty();
        }

        private void Dirty() {
            NotifyChanged();
        }

        public void NotifyChanged() {
            if (KHBD.Context.IsApplicationQuit) {
                return;
            }
            Changed?.Invoke();
        }

        T IReadOnlyListData<T>.Get(int index) {
            return m_List[index];
        }

        bool IReadOnlyListData<T>.TryGet(int index, out T item) {
            if (m_List.Count > index && index > -1) {
                item = m_List[index];
                return true;
            }
            item = default;
            return false;
        }

        int IList<T>.IndexOf(T item) => m_List.IndexOf(item);

        void ICollection<T>.CopyTo(T[] array, int arrayIndex) => m_List.CopyTo(array, arrayIndex);

        IListData<T> IReadOnlyListData<T>.AsReadWrite() => this;

        IReadOnlyListData<T> IListData<T>.AsReadOnly() => this;

        protected override void Dispose() {
            if (Cleared != null) {
                Cleared.Clear();
                Cleared = null;
            }
            if (Added != null) {
                Added.Clear();
                Added = null;
            }
            if (Removed != null) {
                Removed.Clear();
                Removed = null;
            }
            if (Updated != null) {
                Updated.Clear();
                Updated = null;
            }
            if (Changed != null) {
                Changed.Clear();
                Changed = null;
            }
            m_List = null;
            m_This = null;
        }
    }
}